Skip to content

fix(adk-middleware): strip nested required so Gemini accepts function declarations with nested object schemas#1664

Draft
tylerslaton wants to merge 1 commit into
mainfrom
tyler/adk-strip-nested-required
Draft

fix(adk-middleware): strip nested required so Gemini accepts function declarations with nested object schemas#1664
tylerslaton wants to merge 1 commit into
mainfrom
tyler/adk-strip-nested-required

Conversation

@tylerslaton
Copy link
Copy Markdown
Contributor

Gemini's function-calling API silently rejects function declarations whose parameter schema carries a required field below the top-level object — e.g. on the items.properties of an array, or on a nested property's own properties. The model emits no error event and no content events, just RUN_STARTED → STATE_SNAPSHOT → RUN_FINISHED, which surfaces as a frozen agent: the user sends a message, no chart/UI ever renders, no text ever streams back.

The most common shape that trips this is Zod's z.array(z.object({...})) when the inner object marks fields as required:

{
  "type": "object",
  "required": ["data"],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {"label": {...}, "value": {...}},
        "required": ["label", "value"]   ← Gemini chokes on this
      }
    }
  }
}

This shape is produced by every CopilotKit gen-UI demo that renders structured data (charts, tables, lists of records). It's also what any backend tool that takes an array of typed records will generate from Pydantic / Zod / similar libraries. The bug has been latent since _clean_schema_for_genai was introduced in #1349 — OpenAI-backed consumers don't hit it because OpenAI's function-calling API accepts required at every nesting level, and simpler ADK demos with flat schemas don't have nested objects to begin with.

Fix: add a _top_level parameter to _clean_schema_for_genai (defaults to True for the outermost call, False on every recursive call). When False, drop any required key. Top-level required is preserved — Gemini does accept it there.

Verified against the CopilotKit gen-ui-tool-based demo: before the fix, render_bar_chart calls returned empty SSE streams; after the fix, TOOL_CALL_START + TOOL_CALL_ARGS containing the structured chart payload are emitted and the chart renders end-to-end.

Tests: four new regression tests in TestCleanSchemaForGenai cover (1) top-level required preservation, (2) the array-of-objects shape, (3) nested-object-property required, (4) arbitrary-depth required. All 41 existing tests in test_client_proxy_tool.py continue to pass.

…on declarations with nested object schemas

Gemini's function-calling API silently rejects function declarations whose
parameter schema carries a `required` field below the top-level object —
e.g. on the `items.properties` of an array, or on a nested property's
own `properties`. The model emits no error event and no content events,
just RUN_STARTED → STATE_SNAPSHOT → RUN_FINISHED, which surfaces as a
frozen agent: the user sends a message, no chart/UI ever renders, no text
ever streams back.

The most common shape that trips this is Zod's `z.array(z.object({...}))`
when the inner object marks fields as required:

```json
{
  "type": "object",
  "required": ["data"],
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {"label": {...}, "value": {...}},
        "required": ["label", "value"]   ← Gemini chokes on this
      }
    }
  }
}
```

This shape is produced by every CopilotKit gen-UI demo that renders
structured data (charts, tables, lists of records). It's also what any
backend tool that takes an array of typed records will generate from
Pydantic / Zod / similar libraries. The bug has been latent since
`_clean_schema_for_genai` was introduced in #1349 — OpenAI-backed
consumers don't hit it because OpenAI's function-calling API accepts
`required` at every nesting level, and simpler ADK demos with flat
schemas don't have nested objects to begin with.

Fix: add a `_top_level` parameter to `_clean_schema_for_genai` (defaults
to True for the outermost call, False on every recursive call). When
False, drop any `required` key. Top-level `required` is preserved —
Gemini does accept it there.

Verified against the CopilotKit `gen-ui-tool-based` demo: before the
fix, `render_bar_chart` calls returned empty SSE streams; after the
fix, TOOL_CALL_START + TOOL_CALL_ARGS containing the structured chart
payload are emitted and the chart renders end-to-end.

Tests: four new regression tests in `TestCleanSchemaForGenai` cover
(1) top-level required preservation, (2) the array-of-objects shape,
(3) nested-object-property required, (4) arbitrary-depth required.
All 41 existing tests in `test_client_proxy_tool.py` continue to pass.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ag-ui-dojo Ready Ready Preview, Comment May 12, 2026 10:39pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1778625358 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1778625358' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1778625358' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1778625358' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1778625358' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1778625358' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1778625358' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1778625358

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: 1f0e2a2

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 12, 2026

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@1664

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@1664

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@1664

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@1664

@ag-ui/middleware-starter

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/middleware-starter@1664

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@1664

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@1664

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@1664

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@1664

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@1664

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@1664

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@1664

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@1664

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@1664

@ag-ui/langroid

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langroid@1664

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@1664

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@1664

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@1664

@ag-ui/server-starter

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/server-starter@1664

@ag-ui/server-starter-all-features

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/server-starter-all-features@1664

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@1664

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@1664

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@1664

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@1664

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@1664

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@1664

commit: 29f835f

@contextablemark contextablemark marked this pull request as draft May 13, 2026 14:09
@contextablemark contextablemark marked this pull request as draft May 13, 2026 14:09
@contextablemark
Copy link
Copy Markdown
Collaborator

Converting back to Draft after discussion with @tylerslaton - unclear whether this is still needed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants